//
//    rsqlib - Internal library for RsQueue.
//    Copyright (C) 2022  Marcus Pedersén marcus@marcux.org
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//


/// Used by client
pub mod client {
    use serde::{Serialize, Deserialize};
    
    /// Represents client command
    #[derive(Debug, Serialize, Deserialize)]
    pub enum RsqCommand {
	AddPara {
            jobname: String,
            jobid: bool,
            depend: Vec<u64>,
            task: Vec<String>,
            cores: u64,
            nodes: u64
	},
	AddSeq {
            jobname: String,
            jobid: bool,
            depend: Vec<u64>,
            task: Vec<String>
	},
	CancelJob { id: u64 },
	ConfNodeSet { nodename: String, online: bool },
	ConfValidate,
	ShowHistory { long: bool, limit: u64 },
	ShowJob { details: bool, id: u64 },
	ShowQueue { long: bool },
	ShowStatus,
    }


    impl Clone for RsqCommand {
	fn clone(&self) -> RsqCommand {
	    match self {
		RsqCommand::AddPara {
		    jobname: jn,
		    jobid: ji,
		    depend: d,
		    task: t,
		    cores: c,
		    nodes: n
		} => RsqCommand::AddPara {
		    jobname: jn.to_string(),
		    jobid: *ji,
		    depend: d.clone(),
		    task: t.clone(),
		    cores: *c,
		    nodes: *n
		},
		RsqCommand::AddSeq {
		    jobname: jn,
		    jobid: ji,
		    depend: d,
		    task: t
		} => RsqCommand::AddSeq {
		    jobname: jn.to_string(),
		    jobid: *ji,
		    depend: d.clone(),
		    task: t.clone()
		},
		RsqCommand::CancelJob { id: i } => RsqCommand::CancelJob { id: *i },
		RsqCommand::ConfNodeSet { nodename: nn, online: ol } =>
		    RsqCommand::ConfNodeSet { nodename: nn.to_string(), online: *ol },
		RsqCommand::ConfValidate  =>
		    RsqCommand::ConfValidate,
		RsqCommand::ShowHistory { long: lo, limit: li } =>
		    RsqCommand::ShowHistory { long: *lo, limit: *li },
		RsqCommand::ShowJob { details: d, id: i } =>
		    RsqCommand::ShowJob { details: *d, id: *i },
		RsqCommand::ShowQueue { long: l } =>
		    RsqCommand::ShowQueue { long: *l },
		RsqCommand::ShowStatus => RsqCommand::ShowStatus,
 	    }
	}
    }
    

    /// The client request that should
    /// be sent to RsQueue daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqRequest {
	pub cmd: RsqCommand,
	pub validate: String,
    }


    /// Represents RsQueue daemon
    /// response client request
    #[derive(Debug, Serialize, Deserialize)]
    pub enum RsqdResponse {
	AddPara,
	AddSeq,
	CancelJob,
	ConfNodeSet,
	ConfValidate,
	ShowHistory { history: Vec<RsqJob> },
	ShowJob { job: RsqJob },
	ShowQueue { queue: Vec<RsqJob> },
	ShowStatus,
	Err { error: String },
    }

    /// Represents a job
    /// in RsQueue
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqJob {
	pub jobid: u64,
	pub user: String,
	pub name: Option<String>,
	pub init: String,
	pub start: Option<String>,
	pub end: Option<String>,
	pub state: RsqState,
	pub depend: Option<String>,
	pub cores_alloc: Option<u64>,
	pub nodes_alloc: Option<u64>,
	pub nodes: Option<String>,
	pub no_tasks: u64,
	pub no_tasks_exec: u64,
	pub no_tasks_term: u64,
	pub no_tasks_err: u64,
	pub tasks: Vec<RsqTask>,
    }


    /// Represents a task
    /// that is a part of a job
    /// in RsQueue
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqTask {
	pub taskid: u64,
	pub seq_number: Option<u64>,
	pub command: String,
	pub node: Option<String>,
	pub exit_code: i32,
	pub start: Option<String>,
	pub end: Option<String>,
	pub run_time: Option<String>,
	pub stdout: Option<String>,
	pub stderr: Option<String>,
    }
    
    /// Different states
    /// that a job can be in.
    #[derive(Debug, PartialEq, Serialize, Deserialize)]
    pub enum RsqState {
	Queued,
	Running,
	Depend,
	Terminated,
	Cancel,
	Canceled,
	Error,
    }

    impl std::fmt::Display for RsqState {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
	    write!(f, "{:?}", self)
	}
    }
}


/// Used by the node daemons
pub mod node {
    use::serde::{Serialize, Deserialize};

    /// The RsQueue daemon request that should
    /// be sent to RsQueue node daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqdRequest {
        pub cmd: RsqdCommand,
        pub validate: String,
    }
    
    
    /// Represent commands sent from
    /// RsQueue daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub enum RsqdCommand {
        Status,
        ExecuteRsqdJob { job: NodeRsqdJob },
    }
    
    
    /// Represents RsQueue Node daemon
    /// response to the RsQueue daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqdNodeResponse {
        pub data: RsqdNodeData,
        pub validate: String,
    }
    
    
    /// Represents the data that
    /// is sent as a response to
    /// the RsQueue daemon.
    #[derive(Debug, Serialize, Deserialize)]
    pub enum RsqdNodeData {
        NodeStatus {
            total_cpus: usize,
            used_cpus: i32,
            total_memory: u64,
            used_memory: u64,
            total_swap: u64,
            used_swap: u64,
        },
        Error {
            msg: String,
        },
        TaskExecuted {
            jobid: u64,
            task: Vec<u64>,
        },
        StatusTasks {
            tasks: Vec<TaskStatus>,
        }
    }


    /// Represent needed info
    /// on a job for node-rsqd
    /// to be able to run it.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct NodeRsqdJob {
        pub jobid: u64,
        pub cgroup_cores_alloc: Option<String>,
        pub cores_alloc: u64,
        pub memory_alloc: u64,
        pub exec_type: NodeRsqdExecType,
        pub tasks: Vec<NodeRsqdTask>,
        pub user: String,
        pub group: String,
    }

    
    /// Node-rsqd representation
    /// of a task.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct NodeRsqdTask {
        pub taskid: u64,
        pub command: String,
        pub working_directory: String,
    }


    /// What way tasks in a job
    /// should execute, one after
    /// each other or at the same
    /// time.
    #[derive(Debug, PartialEq, Serialize, Deserialize)]
    pub enum NodeRsqdExecType {
        Sequence,
        Parallel
    }

    /// Represent info collected
    /// from running task
    #[derive(Debug, Serialize, Deserialize)]
    pub struct TaskStatus {
        pub jobid: u64,
        pub taskid: u64,
        pub unitname: String,
        pub start_time: String,
        pub end_time: String,
        pub mono_start_time: u64,
        pub mono_end_time: u64,
        pub active_state: String,
        pub sub_state: String,
        pub exit_code: u16,
        pub main_pid: u32,
        pub memory_peak: u64,
        pub pids_peak: u32,
        pub stdout: String,
        pub stderr: String,
    }
    
    impl TaskStatus {
        /// Returns a new empty struct
        pub fn new() -> TaskStatus {
            TaskStatus {
                jobid: 0,
                taskid: 0,
                unitname: String::from(""),
                start_time: String::from(""),
                end_time: String::from(""),
                mono_start_time: 0,
                mono_end_time: 0,
                active_state: String::from(""),
                sub_state: String::from(""),
                exit_code: 0,
                main_pid: 0,
                memory_peak: 0,
                pids_peak: 0,
                stdout: String::from(""),
                stderr: String::from(""),
            }
        }
    }
}


/// Used by all
pub mod all {
    use serde::{Serialize, Deserialize};
    use reqwest::blocking::Client;
    
    /// Request struct sent to RsQueue daemon
    /// for validation with daemon.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqValidateRequest {
        pub password: String,
        pub validate: Option<String>,
    }


    /// Validation response from
    /// RsQueue daemon
    /// Returns the validation string
    /// or the error message in clear text.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct RsqValidateResponse {
        pub validate: Result<String, String>
    }


    /// Initial help text for validation
    /// Checks that effectiv user is root,
    /// otherwise error is returned.
    pub fn validate_print_help() -> Result<(), String> {
        if users::get_effective_uid() != 0 {
            return Err("Permission denied.".to_string());
        }
        
        println!("Please make sure that you have started the validation daemon");
        println!("on the same server as the RsQueue daemon runs.");
        println!("Start validation daemon with command:");
        println!("rsqd --validate");
        println!("");

        Ok(())
    }

    
    /// Function sets up validation
    /// between RsQueue daemon and
    /// this host.
    /// Used by both rsq and node-rsqd
    /// validate agrument is needed for
    /// node-rsqd, rsq should specify None.
    /// Returned string is validate string
    /// retrieved from RsQueue daemon
    pub fn validate_host(port: u16, rsqd_host: String, validate: Option<String>) -> Result<String, String> {
        let url = format!("http://{}:{}/validate", rsqd_host, port);
    
        // get password 
        let password = rpassword::prompt_password("Enter password, same as entered on RsQueue validation daemon: ").unwrap();
    
    
        let valr = RsqValidateRequest {
            password: password,
            validate: validate,
        };
        
        let client = Client::new();
        let res;
        match client.post(url).json(&valr).send() {
            Ok(r) => res = r,
            Err(e) => return Err(format!("Failed to post validate to RsQueue daemon:\n{}", e)),
        }

        match res.json::<RsqValidateResponse>() {
            Ok(v) => v.validate,
            Err(_) => Err(format!("Failed to validate this host!")),
        }
    }




    /// Expect arguments to be fetched
    /// from Cargo.toml in each crate.
    /// env!("CARGO_PKG_AUTHORS")
    /// env!("CARGO_PKG_NAME")
    /// env!("CARGO_PKG_VERSION")
    pub fn print_version(cargo_auths: &str, cargo_name: &str, cargo_version: &str) {
    	let auths = cargo_auths.split(":");
    	let mut authors: Vec<&str> = Vec::new();

    	for a in auths {
    	    if a.len() == 0 {
	        continue;
	    }

	    let name: Vec<&str> = a.split("<").collect();

	    if name.len() >= 1 {
	        authors.push(name[0].trim());
	    }
    	}


    	println!("      __                     __");
    	println!("     /  \\                   /  \\");
    	println!("    /    )                 (    \\");
    	println!("   /     |    0       0    |     \\");
    	println!("  /      |      _____      |      \\");
    	println!(" /       |    _/_____\\_    |       \\");
    	println!("(        |  _/  \\   /  \\_  |        )");
    	println!(" \\      /  |     \\ /     |  \\      /");
    	println!("  \\____/   |      |      |   \\____/");
    	println!("            \\____/ \\____/");
    	println!("               |     |");
    	println!("                \\_|_/");
    	println!("");

    	println!("{} {}", cargo_name, cargo_version);

	if authors.len() >= 1 {
            println!("Copyright (C) 2023  {}", authors[0]);
    	} 
    	else {
    	     println!("Copyright (C) 2023");
    	}
    	println!("License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.");
    	println!("This is free software: you are free to change and redistribute it.");
    	println!("There is NO WARRANTY, to the extent permitted by law.");
    	println!("");

    	if authors.len() == 1 {
            println!("Written by {}.", authors[0]);
    	}
    	else if authors.len() > 1 {
             print!("Written by ");
             for (i, a) in authors.iter().enumerate() {
	     	 if i == 0 {
                     print!("{}", a);
            	 }
	    	 else if i >= authors.len()-1 {
	    	      print!(" and {}", a);
	    	  }
	    	  else {
	    	       print!(", {}", a);
	    	  }
	      }

	      print!(".\n");
    	 }
    }

}
